前面 10 天,我們一路把 Windows Server 巡檢功能加進來。
目前已經做到:
Connectivity
CPU
Memory
Disk
Service
Uptime
Event Log
PowerShell Remoting
CSV Report
Error Handling
Log
功能越來越完整,但新的問題也開始出現。
假設我們繼續把所有功能都塞進:
ServerHealthCheck.ps1
最後可能變成:
ServerHealthCheck.ps1
第 1~50 行 Server List
第 51~120 行 Connectivity
第 121~180 行 CPU
第 181~250 行 Memory
第 251~350 行 Disk
第 351~430 行 Service
第 431~520 行 Event Log
第 521~600 行 CSV
第 601~700 行 Error Handling
程式可能還是可以執行。
但過幾個月再回來看,很可能會變成:
「這一段到底是在幹嘛?」
甚至只是想修改 Disk Threshold,卻不敢碰其他地方。
所以 Day 11 先不增加新的巡檢項目。
今天來做另一件對 Automation 很重要的事情:
把 Script 拆成可以重複使用的 Function。
Function 是什麼?
假設我們有一段程式:
$Time = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content -Path $LogFile
-Value "$Time [INFO] Server Check Started"
每次需要寫 Log,都重新寫一次。
如果一支 Script 有 20 個地方需要 Log,就可能重複 20 次。
比較好的方式是把它包成:
function Write-Log {
param (
$Message
)
$Time = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content `
-Path $LogFile `
-Value "$Time [INFO] $Message"
}
之後只需要:
Write-Log "Server Check Started"
或:
Write-Log "SERVER01 Check Completed"
Function 可以先簡單理解成:
把一段會重複使用的程式包起來,給它一個名字。
為什麼值得拆 Function?
例如現在 Health Check 有:
CPU Check
Memory Check
Disk Check
Service Check
Uptime Check
如果全部混在一起:
HealthCheck.ps1
↓
400~500 行
當 CPU 計算方式有問題時,我可能要在整份 Script 裡找半天。
如果拆成:
Get-CPUHealth
Get-MemoryHealth
Get-DiskHealth
Get-ServiceHealth
Get-UptimeInfo
問題就變得很明確。
CPU 有問題?
看 Get-CPUHealth
Disk 有問題?
看 Get-DiskHealth
這就是 Function 第一個好處:
縮小問題範圍。
PowerShell Function 基本結構
最簡單:
function Show-Hello {
Write-Host "Hello PowerShell"
}
執行:
Show-Hello
得到:
Hello PowerShell
但真正做維運時,Function 通常需要接收資料。
例如:
function Show-ServerName {
param (
$ComputerName
)
Write-Host "Checking $ComputerName"
}
執行:
Show-ServerName -ComputerName "SERVER01"
得到:
Checking SERVER01
param 是什麼?
這裡:
param (
$ComputerName
)
代表:
這個 Function 可以接收一個叫做 $ComputerName 的輸入。
例如:
Show-ServerName -ComputerName "SERVER01"
PowerShell 就會:
SERVER01
↓
$ComputerName
所以同一個 Function 可以:
Show-ServerName -ComputerName "SERVER01"
Show-ServerName -ComputerName "SERVER02"
Show-ServerName -ComputerName "SERVER03"
而不用寫三個 Function。
Function 名稱也有習慣
PowerShell Cmdlet 很常看到:
Get-Service
Get-Process
Test-Connection
New-PSSession
Remove-PSSession
也就是:
Verb-Noun
自己寫 Function 時也建議維持這種方式。
例如:
Get-CPUHealth
Get-MemoryHealth
Get-DiskHealth
Test-ServerRemoting
Write-Log
PowerShell 本身也可以查有哪些建議使用的 Verb:
Get-Verb
這不是說 Function 不這樣命名就不能跑。
而是久了之後:
Get → 取得資料
Test → 測試
New → 建立
Remove → 移除
Set → 修改
看到名字,大概就知道 Function 是做什麼的。
第一個實用 Function:Write-Log
我們 Day 6 已經寫過 Log。
現在正式整理一下:
function Write-Log {
param (
[string]$Message,
[string]$Level = "INFO"
)
$Time = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogMessage = "$Time [$Level] $Message"
Add-Content `
-Path $LogFile `
-Value $LogMessage
}
這次多了一點:
[string]
代表我們預期這個 Parameter 是:
String
文字
另外:
$Level = "INFO"
代表如果沒有指定 Level:
Write-Log "Server Check Started"
預設就是:
INFO
如果:
Write-Log -Message "SERVER01 WinRM Failed"
-Level "ERROR"
才會改成:
2026-09-19 05:10:01 [ERROR] SERVER01 WinRM Failed
第二個 Function:CPU Health
Day 7 的 CPU Check 原本是:
$CPUUsage = [math]::Round(
(
Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage
-Average
).Average,
2
)
接著還有:
if ($CPUUsage -ge 90) {
$CPUStatus = "Critical"
}
elseif ($CPUUsage -ge 80) {
$CPUStatus = "Warning"
}
else {
$CPUStatus = "Normal"
}
現在把它整理成:
function Get-CPUHealth {
$CPUUsage = [math]::Round(
(
Get-CimInstance Win32_Processor |
Measure-Object `
-Property LoadPercentage `
-Average
).Average,
2
)
if ($CPUUsage -ge 90) {
$Status = "Critical"
}
elseif ($CPUUsage -ge 80) {
$Status = "Warning"
}
else {
$Status = "Normal"
}
[PSCustomObject]@{
Usage = $CPUUsage
Status = $Status
}
}
執行:
$CPU = Get-CPUHealth
看看:
$CPU
可能得到:
Usage Status
23 Normal
為什麼 Function 回傳 Object?
我們當然可以:
Write-Host "CPU = $CPUUsage"
但這樣後面的程式不好繼續使用資料。
如果回傳:
[PSCustomObject]@{
Usage = $CPUUsage
Status = $Status
}
那主程式就可以:
$CPU = Get-CPUHealth
接著:
$CPU.Usage
得到:
23
以及:
$CPU.Status
得到:
Normal
未來要輸出 CSV 也很方便。
第三個 Function:Memory Health
接著把 Memory 拆掉。
function Get-MemoryHealth {
$OS = Get-CimInstance Win32_OperatingSystem
$TotalMemory = $OS.TotalVisibleMemorySize
$FreeMemory = $OS.FreePhysicalMemory
$MemoryUsage = [math]::Round(
(
($TotalMemory - $FreeMemory) /
$TotalMemory
) * 100,
2
)
if ($MemoryUsage -ge 90) {
$Status = "Critical"
}
elseif ($MemoryUsage -ge 80) {
$Status = "Warning"
}
else {
$Status = "Normal"
}
[PSCustomObject]@{
Usage = $MemoryUsage
TotalGB = [math]::Round(
$TotalMemory / 1024 / 1024,
2
)
FreeGB = [math]::Round(
$FreeMemory / 1024 / 1024,
2
)
Status = $Status
}
}
執行:
$Memory = Get-MemoryHealth
結果:
Usage TotalGB FreeGB Status
68.31 15.94 5.05 Normal
主程式開始變簡單了
原本 CPU + Memory 可能要寫:
50~100 行
現在主程式只需要:
$CPU = Get-CPUHealth
$Memory = Get-MemoryHealth
後面:
$CPU.Usage
$Memory.Usage
就可以直接使用。
這就是 Function 真正帶來的差別。
第四個 Function:Uptime
Uptime 也很適合拆。
function Get-UptimeInfo {
$OS = Get-CimInstance Win32_OperatingSystem
$LastBootTime = $OS.LastBootUpTime
$Uptime = (Get-Date) - $LastBootTime
if ($Uptime.TotalDays -ge 90) {
$Status = "Warning"
}
else {
$Status = "Normal"
}
[PSCustomObject]@{
LastBootTime = $LastBootTime
UptimeDays = [math]::Round(
$Uptime.TotalDays,
1
)
Status = $Status
}
}
執行:
$Uptime = Get-UptimeInfo
可能:
LastBootTime UptimeDays Status
2026/08/15 03:21 35.1 Normal
第五個 Function:Disk Health
Disk 稍微不一樣。
因為:
CPU
→ 一筆結果
Memory
→ 一筆結果
Disk
→ 可能很多筆
例如:
C:
D:
E:
所以 Function 可以直接回傳多個 Object。
function Get-DiskHealth {
$Disks = Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"
foreach ($Disk in $Disks) {
if ($Disk.Size -eq 0) {
continue
}
$UsedPercent = [math]::Round(
(
($Disk.Size - $Disk.FreeSpace) /
$Disk.Size
) * 100,
2
)
if ($UsedPercent -ge 90) {
$Status = "Critical"
}
elseif ($UsedPercent -ge 80) {
$Status = "Warning"
}
else {
$Status = "Normal"
}
[PSCustomObject]@{
Drive = $Disk.DeviceID
TotalGB = [math]::Round(
$Disk.Size / 1GB,
2
)
FreeGB = [math]::Round(
$Disk.FreeSpace / 1GB,
2
)
UsedPercent = $UsedPercent
Status = $Status
}
}
}
注意這裡沒有:
$Results +=
而是每次迴圈直接輸出:
[PSCustomObject]
PowerShell 會把這些結果收集起來。
所以:
$Disks = Get-DiskHealth
可能得到:
Drive TotalGB FreeGB UsedPercent Status
C: 237.8 67.3 71.70 Normal
D: 465.6 43.1 90.74 Critical
判斷整體 Disk Status
主程式就可以:
if ($Disks.Status -contains "Critical") {
$DiskStatus = "Critical"
}
elseif ($Disks.Status -contains "Warning") {
$DiskStatus = "Warning"
}
else {
$DiskStatus = "Normal"
}
Function 負責:
怎麼取得 Disk 資料?
怎麼計算 UsedPercent?
怎麼判斷每個 Drive?
主程式只負責:
整體 Disk 是什麼狀態?
責任就開始分得比較清楚。
第六個 Function:Service Health
Day 8 我們定義:
$CriticalServices = @(
"EventLog"
"W32Time"
"Winmgmt"
)
今天可以把 Service List 當成 Function 的 Parameter。
function Get-ServiceHealth {
param (
[string[]]$ServiceNames
)
foreach ($ServiceName in $ServiceNames) {
$Service = Get-Service `
-Name $ServiceName `
-ErrorAction SilentlyContinue
if ($null -eq $Service) {
$Status = "NotFound"
}
else {
$Status = $Service.Status.ToString()
}
[PSCustomObject]@{
ServiceName = $ServiceName
Status = $Status
}
}
}
執行:
$CriticalServices = @(
"EventLog"
"W32Time"
"Winmgmt"
)
$Services = Get-ServiceHealth `
-ServiceNames $CriticalServices
結果:
ServiceName Status
EventLog Running
W32Time Running
Winmgmt Running
如果:
W32Time = Stopped
就可以:
$ProblemServices = @(
$Services |
Where-Object {
$_.Status -ne "Running"
}
)
這裡我特別加:
@()
讓結果即使只有一筆,也比較方便使用:
$ProblemServices.Count
Service Function 的價值開始出來了
假設今天是 Domain Controller:
$CriticalServices = @(
"NTDS"
"DNS"
"Netlogon"
"KDC"
"DFSR"
)
Web Server:
$CriticalServices = @(
"W3SVC"
"WAS"
)
SQL:
$CriticalServices = @(
"MSSQLSERVER"
"SQLSERVERAGENT"
)
Function 完全不用改。
只換:
Input
就可以處理不同角色的 Server。
這才是 Function 真正值得做的地方:
邏輯可以重複使用,但輸入可以改變。
Connection 也拆成 Function
Day 10 我們使用:
Test-WSMan
可以整理:
function Test-ServerRemoting {
param (
[string]$ComputerName
)
try {
Test-WSMan `
-ComputerName $ComputerName `
-ErrorAction Stop |
Out-Null
return $true
}
catch {
return $false
}
}
使用:
if (Test-ServerRemoting -ComputerName "SERVER01") {
Write-Host "WinRM OK"
}
else {
Write-Host "WinRM Failed"
}
主程式已經完全不用知道:
Test-WSMan 怎麼寫
try/catch 怎麼處理
只要知道:
True / False
但這裡有一個 PowerShell Remoting 的重點
假設我們在管理端定義:
function Get-CPUHealth {
...
}
然後:
Invoke-Command -ComputerName SERVER01
-ScriptBlock {
Get-CPUHealth
}
不一定可以直接用。
原因是:
在本機定義的 Function,不會自動出現在 Remote PowerShell Session 裡。
本機:
ADMIN-PC
│
├── Get-CPUHealth
├── Get-MemoryHealth
└── Get-DiskHealth
SERVER01 的 Remote Session 並不知道這些 Function 存在。
那遠端巡檢該怎麼辦?
目前我們可以先採用一個比較好理解的方法:
把遠端需要的 Function 定義放進 Remote ScriptBlock。
例如:
Invoke-Command -ComputerName SERVER01
-ScriptBlock {
function Get-CPUHealth {
# CPU Function
}
function Get-MemoryHealth {
# Memory Function
}
$CPU = Get-CPUHealth
$Memory = Get-MemoryHealth
}
這樣 Function 是:
Remote Session 建立
↓
定義 Function
↓
執行 Function
↓
回傳結果
之後再進階,我們可以把 Function:
拆成 .ps1
做成 Module
載入 Module
但 Day 11 先不要一次跳太遠。
建立 Get-ServerHealth
接下來可以再往上包一層。
建立一個:
Get-ServerHealth
專門負責對某台 Server 執行巡檢。
function Get-ServerHealth {
param (
[Parameter(Mandatory)]
[string]$ComputerName
)
try {
Test-WSMan `
-ComputerName $ComputerName `
-ErrorAction Stop |
Out-Null
$Result = Invoke-Command `
-ComputerName $ComputerName `
-ErrorAction Stop `
-ScriptBlock {
# =========================
# CPU
# =========================
function Get-CPUHealth {
$Usage = [math]::Round(
(
Get-CimInstance Win32_Processor |
Measure-Object `
-Property LoadPercentage `
-Average
).Average,
2
)
if ($Usage -ge 90) {
$Status = "Critical"
}
elseif ($Usage -ge 80) {
$Status = "Warning"
}
else {
$Status = "Normal"
}
[PSCustomObject]@{
Usage = $Usage
Status = $Status
}
}
# =========================
# Memory
# =========================
function Get-MemoryHealth {
$OS = Get-CimInstance Win32_OperatingSystem
$Usage = [math]::Round(
(
(
$OS.TotalVisibleMemorySize -
$OS.FreePhysicalMemory
) /
$OS.TotalVisibleMemorySize
) * 100,
2
)
if ($Usage -ge 90) {
$Status = "Critical"
}
elseif ($Usage -ge 80) {
$Status = "Warning"
}
else {
$Status = "Normal"
}
[PSCustomObject]@{
Usage = $Usage
Status = $Status
}
}
# =========================
# Uptime
# =========================
function Get-UptimeInfo {
$OS = Get-CimInstance Win32_OperatingSystem
$Uptime =
(Get-Date) - $OS.LastBootUpTime
[PSCustomObject]@{
LastBootTime =
$OS.LastBootUpTime
UptimeDays =
[math]::Round(
$Uptime.TotalDays,
1
)
}
}
# =========================
# Execute Functions
# =========================
$CPU = Get-CPUHealth
$Memory = Get-MemoryHealth
$Uptime = Get-UptimeInfo
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
Connection = "Success"
CPUUsage = $CPU.Usage
CPUStatus = $CPU.Status
MemoryUsage = $Memory.Usage
MemoryStatus = $Memory.Status
LastBootTime =
$Uptime.LastBootTime
UptimeDays =
$Uptime.UptimeDays
ErrorMessage = ""
CheckTime = Get-Date
}
}
return $Result
}
catch {
return [PSCustomObject]@{
ComputerName = $ComputerName
Connection = "Failed"
CPUUsage = $null
CPUStatus = "Unknown"
MemoryUsage = $null
MemoryStatus = "Unknown"
LastBootTime = $null
UptimeDays = $null
ErrorMessage =
$_.Exception.Message
CheckTime = Get-Date
}
}
}
現在主程式就可以變得非常短。
主程式終於開始像主程式了
以前:
幾百行全部塞在一起
現在:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
$Results = @()
foreach ($Server in $Servers) {
Write-Host "Checking $Server..."
$Result = Get-ServerHealth `
-ComputerName $Server
$Results += $Result
}
就這樣。
真正的主流程非常清楚:
讀取 Server List
↓
一台一台處理
↓
Get-ServerHealth
↓
收集 Results
不用再看到:
CPU 計算
Memory 計算
CIM
if
try
catch
全部混在主程式裡。
再建立 Overall Status
拿回資料後可以:
foreach ($Result in $Results) {
if ($Result.Connection -eq "Failed") {
$OverallStatus = "Unknown"
}
elseif (
$Result.CPUStatus -eq "Critical" -or
$Result.MemoryStatus -eq "Critical"
) {
$OverallStatus = "Critical"
}
elseif (
$Result.CPUStatus -eq "Warning" -or
$Result.MemoryStatus -eq "Warning"
) {
$OverallStatus = "Warning"
}
else {
$OverallStatus = "Healthy"
}
$Result |
Add-Member `
-NotePropertyName "OverallStatus" `
-NotePropertyValue $OverallStatus
}
結果:
Server CPU Memory Connection Overall
SERVER01 21% 62% Success Healthy
SERVER02 85% 71% Success Warning
SERVER03 Failed Unknown
現在:
Connection Failed
不會被誤判成:
Critical
而是:
Unknown
這個邏輯延續 Day 10。
最後輸出 CSV
主程式最後:
$Date = Get-Date -Format "yyyyMMdd"
$ReportFile =
"C:\Temp\Server_Health_$Date.csv"
$Results |
Select-Object ComputerName, Connection, CPUUsage, CPUStatus, MemoryUsage, MemoryStatus, LastBootTime, UptimeDays, OverallStatus, ErrorMessage, CheckTime | Export-Csv
-Path $ReportFile -NoTypeInformation
-Encoding UTF8
主程式現在真正負責的事情變成:
Server List
↓
呼叫巡檢 Function
↓
Overall Status
↓
Export CSV
這樣才比較容易維護。
Function 最好只做一件主要事情
例如:
Get-CPUHealth
最好就是:
取得 CPU Health。
不要最後變成:
Get-CPUHealth
取得 CPU
檢查 Disk
Restart Service
寄 Email
寫 Database
刪 Log
Function 什麼都做的結果,就是重新製造另一個:
巨大 Function。
比較理想的概念是:
Get-CPUHealth
→ CPU
Get-MemoryHealth
→ Memory
Get-DiskHealth
→ Disk
Get-ServiceHealth
→ Service
Write-Log
→ Log
Get-ServerHealth
→ 整合
每一層責任比較清楚。
Threshold 也不應該永遠寫死
目前 Function 裡:
if ($Usage -ge 90)
直接寫死:
90
可以運作。
但未來不同環境可能希望:
CPU Warning = 70
CPU Critical = 90
另一套環境:
CPU Warning = 80
CPU Critical = 95
這時候可以把 Threshold 變成 Parameter。
例如:
function Get-CPUHealth {
param (
[int]$WarningThreshold = 80,
[int]$CriticalThreshold = 90
)
$Usage = ...
}
執行:
Get-CPUHealth
使用預設:
80 / 90
也可以:
Get-CPUHealth -WarningThreshold 70
-CriticalThreshold 85
Function 就更容易重複使用。
Parameter 可以設定 Mandatory
剛才 Get-ServerHealth 裡有:
[Parameter(Mandatory)]
[string]$ComputerName
代表:
ComputerName 一定要提供。
如果輸入:
Get-ServerHealth
PowerShell 就會要求:
Supply values for the following parameters:
ComputerName:
因為沒有 ComputerName,這個 Function 根本不知道要巡哪一台。
這比等到程式跑一半才:
ComputerName 是空的
更好。
Function 不是越多越好
這也是值得注意的一點。
不要為了「看起來很專業」:
一行程式
↓
包 Function
另外兩行
↓
再包 Function
Function 真正適合出現的地方通常是:
會重複使用
邏輯可以獨立
值得單獨測試
主程式因此更容易理解
如果 Function 拆得太碎,反而會變成:
Function A
↓
Function B
↓
Function C
↓
Function D
然後想知道一個結果,必須跳來跳去。
所以我們追求的不是:
Function 越多越好。
而是:
每個 Function 有清楚的責任。
怎麼測試 Function?
這也是拆 Function 很大的好處。
以前要測 CPU:
整支 HealthCheck.ps1 全部跑一次
現在只需要:
Get-CPUHealth
Memory:
Get-MemoryHealth
Disk:
Get-DiskHealth
Server:
Get-ServerHealth `
-ComputerName "SERVER01"
如果 Server02 有問題,也只測:
Get-ServerHealth `
-ComputerName "SERVER02"
這就是:
把問題縮小。
對維運工具來說特別重要。
今天整理後的 Script 架構
如果目前還只使用一個 .ps1,可以先長這樣:
ServerHealthCheck.ps1
01 Configuration
│
├── Server List
├── Threshold
├── Report Path
└── Critical Service
02 Functions
│
├── Write-Log
├── Test-ServerRemoting
├── Get-CPUHealth
├── Get-MemoryHealth
├── Get-DiskHealth
├── Get-ServiceHealth
├── Get-UptimeInfo
└── Get-ServerHealth
03 Main
│
├── foreach Server
├── Get-ServerHealth
└── Collect Results
04 Report
│
├── Overall Status
├── Export-Csv
└── Write-Log
雖然還是一個 .ps1,
但至少已經從:
一大坨程式
變成:
Configuration
Functions
Main
Report
結構清楚很多。
下一階段還可以再拆檔案
未來甚至可以變成:
ServerHealthCheck/
│
├── Main.ps1
│
├── Config/
│ └── Servers.csv
│
├── Functions/
│ ├── Get-CPUHealth.ps1
│ ├── Get-MemoryHealth.ps1
│ ├── Get-DiskHealth.ps1
│ ├── Get-ServiceHealth.ps1
│ ├── Get-UptimeInfo.ps1
│ └── Write-Log.ps1
│
├── Reports/
│
└── Logs/
再往前甚至可以做成:
PowerShell Module
例如:
Import-Module SysAdminToolkit
之後直接:
Get-ServerHealth
Get-DiskHealth
Get-ServiceHealth
但這已經是下一階段的事情。
Day 11 先把 Function 的概念弄清楚就好。
Day 11 小結
今天沒有新增任何新的 Windows Server 監控項目。
但我認為今天其實是前 11 天裡很重要的一篇。
因為前面一直在做:
功能
功能
功能
功能
今天第一次停下來思考:
這套 Script 半年後我自己還改得動嗎?
我們開始把:
CPU
Memory
Disk
Service
Uptime
Connection
Log
拆成獨立 Function。
主程式也開始從:
幾百行邏輯
變成:
foreach ($Server in $Servers) {
$Result = Get-ServerHealth `
-ComputerName $Server
$Results += $Result
}
這才是我希望最後這套工具的樣子。
簡單來說:
以前:
Main Script
├── CPU 細節
├── Memory 細節
├── Disk 細節
├── Service 細節
└── 一堆判斷
現在:
Main Script
│
├── Get-CPUHealth
├── Get-MemoryHealth
├── Get-DiskHealth
├── Get-ServiceHealth
├── Get-UptimeInfo
└── Write-Log
Function 不只是讓程式比較漂亮。
真正的價值是:
讓每一個問題都有比較清楚的位置可以找。
這其實跟系統維運本身的思維很像。
遇到問題時,不是整台 Server 從頭亂查。
而是:
先分類
↓
縮小範圍
↓
定位問題
↓
處理
寫 Automation Script 也是一樣。
Day 12 預告
Day 12|Server 清單不要寫死:用 CSV 管理不同角色與巡檢設定
現在我們還有一個地方寫死在 Script:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
而且不同 Server 其實不應該使用完全相同的巡檢規則。
例如:
DC01
Role = DomainController
Services = NTDS / DNS / Netlogon / KDC
WEB01
Role = WebServer
Services = W3SVC / WAS
SQL01
Role = Database
Services = MSSQLSERVER / SQLSERVERAGENT
所以 Day 12 我們會開始把:
Server Name
Role
Environment
CPU Threshold
Memory Threshold
Critical Service
從 Script 裡抽出去,建立:
Servers.csv
最後讓程式從:
Import-Csv
自動決定:
這台 Server 是什麼角色,又應該檢查什麼。
到這一步,我們的巡檢工具就會開始從「寫死的 Script」,走向真正比較容易維護的 Configuration-driven Automation。